package com.example.stripe_terminal_tap_to_pay;

import static java.sql.DriverManager.println;

import android.Manifest;
import android.app.Activity;
import android.app.Application;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.os.StrictMode;


import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.google.firebase.crashlytics.buildtools.reloc.com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.s.ai.Stripe;
import com.stripe.stripeterminal.Terminal;
import com.stripe.stripeterminal.TerminalApplicationDelegate;
import com.stripe.stripeterminal.external.callable.Callback;
import com.stripe.stripeterminal.external.callable.Cancelable;
import com.stripe.stripeterminal.external.callable.ConnectionTokenCallback;
import com.stripe.stripeterminal.external.callable.ConnectionTokenProvider;
import com.stripe.stripeterminal.external.callable.DiscoveryListener;
import com.stripe.stripeterminal.external.callable.PaymentIntentCallback;
import com.stripe.stripeterminal.external.callable.ReaderCallback;
import com.stripe.stripeterminal.external.callable.TerminalListener;
import com.stripe.stripeterminal.external.models.CollectConfiguration;
import com.stripe.stripeterminal.external.models.ConnectionConfiguration;
import com.stripe.stripeterminal.external.models.ConnectionStatus;
import com.stripe.stripeterminal.external.models.ConnectionTokenException;
import com.stripe.stripeterminal.external.models.DiscoveryConfiguration;
import com.stripe.stripeterminal.external.models.DiscoveryMethod;
import com.stripe.stripeterminal.external.models.PaymentIntent;
import com.stripe.stripeterminal.external.models.PaymentIntentParameters;
import com.stripe.stripeterminal.external.models.PaymentStatus;
import com.stripe.stripeterminal.external.models.Reader;
import com.stripe.stripeterminal.external.models.TerminalException;
import com.stripe.stripeterminal.log.LogLevel;

import org.jetbrains.annotations.NotNull;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.flutter.app.FlutterActivityEvents;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;

/** StripeTerminalTapToPayPlugin */
public class StripeTerminalTapToPayPlugin extends Application implements FlutterPlugin, MethodCallHandler, PluginRegistry.RequestPermissionsResultListener, ActivityAware, FlutterActivityEvents {
  private static final int REQUEST_CODE_LOCATION = 1012;
  /// The MethodChannel that will the communication between Flutter and native Android
  ///
  /// This local reference serves to register the plugin with the Flutter Engine and unregister it
  /// when the Flutter Engine is detached from the Activity
  private Activity currentActivity = new Activity();
  private List<String> permissions;
  private MethodChannel channel;
  /**stripe localId*/
  private String localId;
  /**获取stripeToken服务器Url*/
  private String result;
  private Cancelable discoveryCancelable;
  private BluetoothAdapter adapter;
  private PaymentIntent currentPaymentIntent;
  private Cancelable collectPaymentMethod;

  @Override
  public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
    channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "stripe_terminal_tap_to_pay");
    channel.setMethodCallHandler(this);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
      String[] array = {Manifest.permission.ACCESS_FINE_LOCATION,
              Manifest.permission.BLUETOOTH,
              Manifest.permission.BLUETOOTH_ADMIN,
              Manifest.permission.BLUETOOTH_SCAN,
              Manifest.permission.BLUETOOTH_CONNECT,
              Manifest.permission.INTERNET,
              Manifest.permission.ACCESS_NETWORK_STATE,
              Manifest.permission.ACCESS_WIFI_STATE,
              Manifest.permission.READ_PHONE_STATE
      };
      permissions = Arrays.asList(array);
    } else {
      String[] array = {Manifest.permission.ACCESS_FINE_LOCATION,
              Manifest.permission.BLUETOOTH,
              Manifest.permission.BLUETOOTH_ADMIN,
              Manifest.permission.INTERNET,
              Manifest.permission.ACCESS_NETWORK_STATE,
              Manifest.permission.ACCESS_WIFI_STATE,
              Manifest.permission.READ_PHONE_STATE,
              Manifest.permission.ACCESS_COARSE_LOCATION
      };
      permissions = Arrays.asList(array);
    }
  }

  @Override
  public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
    switch (call.method) {
      case "permissionAllowed":
        _isPermissionAllowed(result);
        break;
      case "init":
        this.localId = call.argument("localId");
        this.result = call.argument("result");
        if (_isPermissionAllowed(result)) {
          init();
        }
        break;
      case "pay":
        String clientSecret = call.argument("clientSecret");
        if (clientSecret != null) {
          pay(clientSecret,result);
        }
        break;
      case "cancelRetrievePaymentIntent":
        cancelRetrievePaymentIntent(result);
        break;
      case "cancelProcessPayment":
        cancelProcessPayment(result);
        break;

    }

  }

  @Override
  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
    channel.setMethodCallHandler(null);
  }

  @Override
  public void onCreate(Bundle bundle) {
    // Should happen before super.onCreate()
    StrictMode.setThreadPolicy(
            new StrictMode.ThreadPolicy.Builder()
                    .detectDiskReads()
                    .detectDiskWrites()
                    .detectAll()
                    .penaltyLog()
                    .build());

    StrictMode.setVmPolicy(
            new StrictMode.VmPolicy.Builder()
                    .detectLeakedSqlLiteObjects()
                    .detectLeakedClosableObjects()
                    .penaltyLog()
                    .build());
    super.onCreate();
    TerminalApplicationDelegate.onCreate(this);
  }

  @Override
  public void onNewIntent(Intent intent) {

  }

  @Override
  public void onPause() {

  }

  @Override
  public void onStart() {
    final DiscoveryConfiguration config = new DiscoveryConfiguration(20, DiscoveryMethod.LOCAL_MOBILE, false);

    if (ActivityCompat.checkSelfPermission(currentActivity.getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
      // TODO: Consider calling
      //    ActivityCompat#requestPermissions
      // here to request the missing permissions, and then overriding
      //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
      //                                          int[] grantResults)
      // to handle the case where the user grants the permission. See the documentation
      // for ActivityCompat#requestPermissions for more details.
      return;
    }
    discoveryCancelable = Terminal.getInstance().discoverReaders(
            config,
            readers -> {
              if (!readers.isEmpty()) {
                connection(readers.get(0));
              }
              // Automatically connect to supported mobile readers
            },
            new Callback() {
              @Override
              public void onSuccess() {
                System.out.println("Finished discovering readers");

              }
              @Override
              public void onFailure(@NonNull TerminalException e) {
                currentActivity.runOnUiThread(new Runnable() {
                  @Override
                  public void run() {
                    channel.invokeMethod("onErrorLog", "discoverReadersStart"+e.getErrorMessage());
                  }
                });
                onStart();
              }
            }
    );
  }

  @Override
  public void onResume() {

  }

  @Override
  public void onPostResume() {

  }

  @Override
  public void onDestroy() {

  }

  @Override
  public void onStop() {
    // 如果您要离开活动或片段而不选择读者
    // 请确保取消发现过程，否则 SDK 将卡在
    //“发现读者”阶段
    if (discoveryCancelable != null) {
      discoveryCancelable.cancel(new Callback() {
        @Override
        public void onSuccess() {
          discoveryCancelable = null;
        }

        @Override
        public void onFailure(@NonNull TerminalException e) {
          discoveryCancelable = null;
          currentActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
              channel.invokeMethod("onErrorLog", "discoverReadersStop"+e.getErrorMessage());
            }
          });
        }
      });
    }
  }

  @Override
  public boolean onBackPressed() {
    return false;
  }

  @Override
  public void onUserLeaveHint() {

  }

  @Override
  public void onWindowFocusChanged(boolean b) {

  }

  @Override
  public void onTrimMemory(int i) {
    super.onTrimMemory(i);
  }

  @Override
  public void onConfigurationChanged(@NonNull Configuration configuration) {
    super.onConfigurationChanged(configuration);
  }

  @Override
  public void onLowMemory() {
    super.onLowMemory();
  }

  @Override
  public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) {
    System.out.println("getActivity");
    currentActivity = activityPluginBinding.getActivity();
    activityPluginBinding.addRequestPermissionsResultListener(this);
  }

  @Override
  public void onDetachedFromActivityForConfigChanges() {
    currentActivity = null;
  }

  @Override
  public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding activityPluginBinding) {
    System.out.println("getActivity");
    currentActivity = activityPluginBinding.getActivity();
  }

  @Override
  public void onDetachedFromActivity() {
    currentActivity = null;
  }

  @Override
  public boolean onActivityResult(int i, int i1, @Nullable Intent intent) {
    return false;
  }

  @Override
  public boolean onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == REQUEST_CODE_LOCATION && grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
      throw new RuntimeException("Location services are required in order to connect to a reader.");
    }
    List<Integer> permissionStatus = new ArrayList<>();
    for (String item : permissions) {
      permissionStatus.add(ContextCompat.checkSelfPermission(currentActivity, item));
    }
    return requestCode == REQUEST_CODE_LOCATION;
  }


  private void connection(Reader connectionReader) {
    Terminal.getInstance().connectLocalMobileReader(connectionReader, new ConnectionConfiguration.LocalMobileConnectionConfiguration(localId), new ReaderCallback() {
      @Override
      public void onSuccess(@NonNull Reader reader) {
        onStop();
        System.out.println("Connected to mobile device");
        currentActivity.runOnUiThread(new Runnable() {
          @Override
          public void run() {
            channel.invokeMethod("onSuccessNFC", "连接NFC成功");
          }
        });
      }

      @Override
      public void onFailure(@NonNull TerminalException e) {
        System.out.println("----------------------------onSuccessNFConSuccessNFC----------------------------------------");
        currentActivity.runOnUiThread(new Runnable() {
          @Override
          public void run() {
            channel.invokeMethod("onSuccessNFC", "discoverReadersStart" + e.getErrorMessage());
          }
        });
      }
    });
  }


  public void pay(String clientSecret,Result result){
    Terminal.getInstance().retrievePaymentIntent(clientSecret, new PaymentIntentCallback() {
      @Override
      public void onSuccess(@NonNull PaymentIntent paymentIntent) {
        currentPaymentIntent = paymentIntent;
        collectPaymentMethod = Terminal.getInstance().collectPaymentMethod(paymentIntent,
                new PaymentIntentCallback() {
                  @Override
                  public void onSuccess(@NonNull PaymentIntent paymentIntent) {
                    currentPaymentIntent = paymentIntent;
                    // Placeholder for confirming paymentIntent
                    System.out.println("----------------------------2222222222222----------------------------------------");
                    Terminal.getInstance().processPayment(paymentIntent,
                            new PaymentIntentCallback() {
                              @Override
                              public void onSuccess(@NonNull PaymentIntent paymentIntent) {
                                currentPaymentIntent = null;
                                // Placeholder for notifying your backend to capture paymentIntent.id
                                //TODO : Return to previous Screen
                                Gson gson = new Gson();
                                result.success(gson.fromJson(gson.toJson(paymentIntent),new TypeToken<Map<String, Object>>(){}.getType()));
                              }
                              @Override
                              public void onFailure(@NonNull TerminalException e) {
                                currentPaymentIntent = null;
                                Map<String,Object> data = new HashMap<>();
                                data.put("status","SWIPE_FAILED");
                                result.success(data);
                                // Placeholder for handling the exception
                                System.out.println("----------------------------333333333333----------------------------------------");
                                currentActivity.runOnUiThread(new Runnable() {
                                  @Override
                                  public void run() {
                                    channel.invokeMethod("onPayMessage", "PayMessage: " + e.getErrorMessage());
                                  }
                                });
                              }
                            });
                  }

                  @Override
                  public void onFailure(TerminalException exception) {
                    Map<String,Object> data = new HashMap<>();
                    data.put("status","CANCEL_PAYMENT");
                    result.success(data);

                    System.out.println("----------------------------444444444444444----------------------------------------");
                    // Placeholder for handling exception
                  }
                });
      }
      @Override
      public void onFailure(TerminalException exception) {
        System.out.println("----------------------------55555555555555555555----------------------------------------");
        // Placeholder for handling exception
      }
    });
  }

  /**
   * 取消付款意向
   */
  private void cancelProcessPayment(Result result) {
    if (currentPaymentIntent == null) {
      result.error(
              "stripeTerminal#unableToProcessPayment",
              "Stripe reader was not able to process the payment for the provided payment intent",
              null
      );
      return;
    }
    Terminal.getInstance()
            .cancelPaymentIntent(
                    currentPaymentIntent,
                    new PaymentIntentCallback() {
                      @Override
                      public void onSuccess(PaymentIntent intent) {

                        currentPaymentIntent = null;
                        Gson gson = new Gson();
                        result.success(gson.fromJson(gson.toJson(intent),new TypeToken<Map<String, Object>>(){}.getType()));
                      }

                      @Override
                      public void onFailure(TerminalException e) {
                        currentPaymentIntent = null;
                        result.error(
                                "stripeTerminal#unableToProcessPayment",
                                "Stripe reader was not able to process the payment for the provided payment intent. ${e.errorMessage}",
                                e.getErrorMessage()
                        );
                      }
                    });
  }

  /**
   * 取消检索刷卡状态
   */
  private void cancelRetrievePaymentIntent(Result result) {
    if (collectPaymentMethod == null) {
      result.error(
              "stripeTerminal#unabelToCancelDiscover",
              "There is no discover action running to stop.",
              null
      );
    } else {
      collectPaymentMethod.cancel(new Callback() {
        @Override
        public void onFailure(@NonNull TerminalException e) {
          result.error(
                  "stripeTerminal#unabelToCancelDiscover",
                  "Unable to stop the discover action because ${e.errorMessage}",
                  e.getErrorMessage()
          );
        }

        @Override
        public void onSuccess() {
          result.success(true);
        }
      });
      collectPaymentMethod = null;
    }
  }

  private Boolean _isPermissionAllowed(Result result) {
    List<Integer> permissionStatus = new ArrayList<>();
    List<Boolean> cannotAskPermissions = new ArrayList<>();
    for (String item : permissions) {
      permissionStatus.add(ContextCompat.checkSelfPermission(currentActivity, item));
      cannotAskPermissions.add(ActivityCompat.shouldShowRequestPermissionRationale(currentActivity, item));
    }
    final BluetoothManager bluetoothManager =(BluetoothManager) currentActivity.getApplicationContext().getSystemService(Context.BLUETOOTH_SERVICE);
    adapter  = bluetoothManager != null ? bluetoothManager.getAdapter() : null;
    if (!adapter.isEnabled()) {
      adapter.enable();
    }
    if (!permissionStatus.contains(PackageManager.PERMISSION_DENIED)) {
      return true;
    }
    if (cannotAskPermissions.contains(true)) {
      return false;
    }
    ActivityCompat.requestPermissions(currentActivity,
            (String[]) permissions.toArray(),
            REQUEST_CODE_LOCATION);
    final LocationManager locationManager =
            (LocationManager) currentActivity.getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
    boolean gpsEnabled = false;
    try {
      gpsEnabled = locationManager != null &&
              locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
    } catch (Exception exception) {
    }
    result.success(gpsEnabled);
    return gpsEnabled;
  }

  private Map<String, String> stringToMap(String param) {
    Map<String, String> map = new HashMap<>();
    //去除{}
    String s1 = param.replace("{", "");
    String s2 = s1.replace("}", "");
    String s3 = s2.trim();
    //1.根据逗号分隔
    String[] split = s3.split(",");
    for (int i = split.length - 1; i >= 0; i--) {
      String trim = split[i].trim();
      String[] split1 = trim.split(":");
      map.put(split1[0].replace("\"",""),split1[1].replace("\"",""));
    }
    return map;
  }

  private void init() {
    try {
      if (!Terminal.isInitialized()) {
        Terminal.initTerminal(currentActivity.getApplicationContext(), LogLevel.VERBOSE, new ConnectionTokenProvider() {
          @Override
          public void fetchConnectionToken(@NonNull ConnectionTokenCallback connectionTokenCallback) {
            connectionTokenCallback.onSuccess(result);
          }
        }, new TerminalListener() {
          @Override
          public void onUnexpectedReaderDisconnect(@NonNull Reader reader) {
            onStart();
          }

          @Override
          public void onConnectionStatusChange(@NotNull ConnectionStatus status) {
            currentActivity.runOnUiThread(new Runnable() {
              @Override
              public void run() {
                channel.invokeMethod("onStatusChange", status.toString());
              }
            });
          }
        });
        onStart();
      }
    }catch (TerminalException e){
      currentActivity.runOnUiThread(new Runnable() {
        @Override
        public void run() {
          channel.invokeMethod("onErrorLog","initTerminal()"+ e.getMessage());
        }
      });
      throw new RuntimeException("Location services are required in order to initialize the Terminal.", e);
    }
  }
}
